/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import * as PConst from '../protocol.const';
import { ITypeScriptServiceClient } from '../typescriptService';
import { tsTextSpanToVsRange } from '../utils/convert';

const outlineTypeTable: { [kind: string]: any } = Object.create(null);


export default class TypeScriptDocumentSymbolProvider {
	public constructor(
		private client: ITypeScriptServiceClient,
		private vscode: any
	) {
		outlineTypeTable[PConst.Kind.module] = this.vscode.SymbolKind.Module;
		outlineTypeTable[PConst.Kind.class] = this.vscode.SymbolKind.Class;
		outlineTypeTable[PConst.Kind.enum] = this.vscode.SymbolKind.Enum;
		outlineTypeTable[PConst.Kind.interface] = this.vscode.SymbolKind.Interface;
		outlineTypeTable[PConst.Kind.memberFunction] = this.vscode.SymbolKind.Method;
		outlineTypeTable[PConst.Kind.memberVariable] = this.vscode.SymbolKind.Property;
		outlineTypeTable[PConst.Kind.memberGetAccessor] = this.vscode.SymbolKind.Property;
		outlineTypeTable[PConst.Kind.memberSetAccessor] = this.vscode.SymbolKind.Property;
		outlineTypeTable[PConst.Kind.variable] = this.vscode.SymbolKind.Variable;
		outlineTypeTable[PConst.Kind.const] = this.vscode.SymbolKind.Variable;
		outlineTypeTable[PConst.Kind.localVariable] = this.vscode.SymbolKind.Variable;
		outlineTypeTable[PConst.Kind.variable] = this.vscode.SymbolKind.Variable;
		outlineTypeTable[PConst.Kind.function] = this.vscode.SymbolKind.Function;
		outlineTypeTable[PConst.Kind.localFunction] = this.vscode.SymbolKind.Function;
	}

	public async provideDocumentSymbols(resource: any, token: any): Promise<any[]> {
		const filepath = this.client.normalizePath(resource.uri);
		if (!filepath) {
			return [];
		}
		const args = {
			file: filepath
		};

		try {
			const result: any[] = [];
			if (this.client.apiVersion.has206Features()) {
				const response = await this.client.execute('navtree', args, token);
				if (response.body) {
					// The root represents the file. Ignore this when showing in the UI
					let tree = response.body;
					if (tree.childItems) {
						tree.childItems.forEach((item: any) => TypeScriptDocumentSymbolProvider.convertNavTree(resource.uri, result, item, this.vscode));
					}
				}
			} else {
				const response = await this.client.execute('navbar', args, token);
				if (response.body) {
					let foldingMap: ObjectMap<any> = Object.create(null);
					response.body.forEach((item: any) => TypeScriptDocumentSymbolProvider.convertNavBar(resource.uri, 0, foldingMap, result, item, this.vscode));
				}
			}
			return result;
		} catch (e) {
			return [];
		}
	}

	private static convertNavBar(resource: any, indent: number, foldingMap: ObjectMap<any>, bucket: any[], item: any, vscode: any, containerLabel?: string): void {
		let realIndent = indent + item.indent;
		let key = `${realIndent}|${item.text}`;
		if (realIndent !== 0 && !foldingMap[key] && TypeScriptDocumentSymbolProvider.shouldInclueEntry(item.text)) {
			let result = new vscode.SymbolInformation(item.text,
				outlineTypeTable[item.kind as string] || vscode.SymbolKind.Variable,
				containerLabel ? containerLabel : '',
				new vscode.Location(resource, tsTextSpanToVsRange(item.spans[0], vscode)));
			foldingMap[key] = result;
			bucket.push(result);
		}
		if (item.childItems && item.childItems.length > 0) {
			for (let child of item.childItems) {
				TypeScriptDocumentSymbolProvider.convertNavBar(resource, realIndent + 1, foldingMap, bucket, child, item.text);
			}
		}
	}

	private static convertNavTree(resource: any, bucket: any[], item: any, vscode: any, containerLabel?: string): void {
		const result = new vscode.SymbolInformation(item.text,
			outlineTypeTable[item.kind as string] || vscode.SymbolKind.Variable,
			containerLabel ? containerLabel : '',
			new vscode.Location(resource, tsTextSpanToVsRange(item.spans[0], vscode))
		);
		if (item.childItems && item.childItems.length > 0) {
			for (const child of item.childItems) {
				TypeScriptDocumentSymbolProvider.convertNavTree(resource, bucket, child, result.name);
			}
		}

		if (TypeScriptDocumentSymbolProvider.shouldInclueEntry(result.name)) {
			bucket.push(result);
		}
	}

	private static shouldInclueEntry(name: string): boolean {
		return !!(name && name !== '<function>' && name !== '<class>');
	}
}